package com.study.controller;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.study.core.error.ErrorUtil;
import com.study.core.resp.Resp;
import com.study.model.dto.FileUploadDto;
import com.study.model.entity.Attachment;
import com.study.service.AttachmentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.CacheControl;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.activation.MimetypesFileTypeMap;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @Description 文件上传下载
 * @Author dangxianyue
 * @Date 2024/5/30 14:52
 */
@Slf4j
@RestController
@RequestMapping("/study-app/attachment")
public class AttachmentController {

    @Autowired
    private AttachmentService attachmentService;

    private String baseDir;
    private String tempDir;

    @Value("${attachment.base-dir:}")
    public void setBaseDir(String baseDir) {
        if (StrUtil.isBlank(baseDir)) {
            baseDir = "/data/attachment/";
        }
        if (!baseDir.endsWith("/")) {
            baseDir = baseDir + "/";
        }
        // String absolutePath = FileUtil.getAbsolutePath(baseDir);
        // File baseDirFile = FileUtil.mkdir(absolutePath);
        // // this.baseDir = baseDirFile.getAbsolutePath();// 这样写是没有/结尾的
        // File tempDirFile = new File(baseDirFile, "temp");
        // FileUtil.mkdir(tempDirFile);
        // this.tempDir = tempDirFile.getAbsolutePath();// 这样写是没有/结尾的
        this.baseDir = baseDir;
        this.tempDir = baseDir + "temp/";
        FileUtil.mkdir(this.baseDir);
        FileUtil.mkdir(this.tempDir);
        log.info("已初始化附件存储目录：{}，临时文件目录：{}", new Object[]{this.baseDir, this.tempDir});
    }

    /**
     * 附件上传
     * @param bizType 业务类型
     * @param bizId 业务id
     * @param fileName 文件名
     * @param multipartFile 附件
     * @return
     */
    @PostMapping(
            value = {"/upload"},
            consumes = {"multipart/form-data"}
    )
    public Resp<?> uploadFile(@RequestParam(required = false) String bizType,
                           @RequestParam(required = false) String bizId,
                           @RequestParam(required = false) String fileName,
                           @ModelAttribute MultipartFile multipartFile) {
        FileUploadDto dto = new FileUploadDto();
        dto.setBizId(bizId);
        dto.setBizType(bizType);
        dto.setFileName(fileName);
        dto.setMultipartFile(multipartFile);
        List<Attachment> list = batchUpload(Arrays.asList(dto));
        // 返回前端
        return Resp.ok(list.get(0));
    }

    @PostMapping(
            value = {"/batchUpload"},
            consumes = {"multipart/form-data"}
    )
    public Resp<?> batchUploadFile(List<MultipartFile> multipartFileList) {
        List<FileUploadDto> dtoList = new ArrayList<>();
        multipartFileList.forEach(o -> dtoList.add(new FileUploadDto().setMultipartFile(o)));
        List<Attachment> list = batchUpload(dtoList);
        // 返回前端
        return Resp.ok(list);
    }

    /**
     * 获取附件（下载/预览）
     * @param attachmentId 附件id
     * @param type 设置响应头类型  "inline"查看  "attachment"下载
     * @return
     */
    @GetMapping(
            value = {"/getAttachment/{attachmentId}"},
            produces = {"application/octet-stream"}
    )
    public ResponseEntity<Resource> getAttachment(@PathVariable("attachmentId") Long attachmentId,
                                                  @RequestParam(required = false) String type) {
        // 判断id不能为空
        ErrorUtil.notNull(attachmentId, "附件id不能为空！");
        // 从数据库获取附件记录
        Attachment attachment = attachmentService.getById(attachmentId);
        if (attachment == null) {
            return ResponseEntity.notFound().build();
        }

        FileSystemResource fileSystemResource = new FileSystemResource(getAbsoluteFilePath(attachment.getStorageRelativePath()));
        if (!fileSystemResource.exists()) {
            return ResponseEntity.notFound().build();
        }

        if (type == null || (!"attachment".equals(type) && !"inline".equals(type))) {
            type = "attachment";
        }

        String fileName = attachment.getOldFileName();
        if (StrUtil.isBlank(fileName)) {
            fileName = attachment.getNewFileName();
        }

        // response.setHeader("Content-disposition", "attachment;filename=" +filename);
        // Content-disposition: 有两个选项：
        //   1、inline表示在页面中打开查看预览，
        //   2、attachment表示以附件方式下载（弹出下载框）
        // 注意:filename如果是中文会出现乱码：解决办法：
        // 　　1、将filename 替换为 new String(filename.getBytes(), "ISO8859-1");
        // 　　2、将filename 替换为 URLEncoder.encode(filename, "utf-8");
        // 原文链接：https://blog.csdn.net/qq_45736175/article/details/108326146
        ContentDisposition disposition = ContentDisposition.builder(type).filename(fileName, StandardCharsets.UTF_8).build();
        HttpHeaders httpHeaders = new HttpHeaders();
        String contentType = getContentType(fileSystemResource.getFile());
        httpHeaders.setContentType(StrUtil.isBlank(contentType) ? MediaType.APPLICATION_OCTET_STREAM : MediaType.parseMediaType(contentType));
        httpHeaders.setContentDisposition(disposition);
        httpHeaders.setCacheControl(CacheControl.noCache());
        return ResponseEntity.ok()
                .headers(httpHeaders)
                .contentLength(fileSystemResource.getFile().length())
                .body(fileSystemResource);
    }


    @DeleteMapping({"/deleteAttachment/{attachmentId}"})
    public Resp<?> delete(@PathVariable("attachmentId") Long attachmentId) {
        // 判断id不能为空
        ErrorUtil.notNull(attachmentId, "附件id不能为空！");
        // 从数据库中查询
        Attachment attachment = attachmentService.getById(attachmentId);
        if (attachment != null) {
            // 从文件系统中删除
            File file = new File(getAbsoluteFilePath(attachment.getStorageRelativePath()));
            file.delete();
            // 删除数据库中的附件记录
            attachmentService.removeById(attachmentId);
        }
        return Resp.ok();
    }

    private List<Attachment> batchUpload(List<FileUploadDto> dtoList){
        List<Attachment> list = new ArrayList<>();
        for (FileUploadDto dto : dtoList) {
            // 判断附件是否为空
            if (dto == null || dto.getMultipartFile() == null || dto.getMultipartFile().isEmpty()) {
                ErrorUtil.throwBizError("附件不能为空！");
            }
            try {
                // 原始附件名
                String originalFilename = StrUtil.isBlank(dto.getFileName()) ? dto.getMultipartFile().getOriginalFilename() : dto.getFileName();
                // 扩展名
                String extName = FileNameUtil.extName(originalFilename);
                // 新文件名
                String newFileName = IdUtil.fastSimpleUUID() + (StrUtil.isBlank(extName) ? "" : "." + extName);
                // 相对路径
                LocalDateTime now = LocalDateTimeUtil.now();
                String nowDateDirPath = "/" + now.getYear() + "/" + now.getMonthValue() + "/" + now.getDayOfMonth() + "/";
                String storageRelativePath = nowDateDirPath + newFileName;
                // 进行上传
                File dest = doUpload(dto.getMultipartFile(), storageRelativePath);
                // 保存到数据库
                // String id = IdUtil.fastSimpleUUID();
                Long id = IdUtil.getSnowflakeNextId();
                Attachment attachment = new Attachment();
                attachment.setId(id);
                attachment.setBizType(dto.getBizType());
                attachment.setBizId(dto.getBizId());
                attachment.setExtName(extName);
                attachment.setOldFileName(originalFilename);
                attachment.setNewFileName(newFileName);
                attachment.setFileSize(dest.length());
                attachment.setMd5(DigestUtil.md5Hex(dest));
                attachment.setStorageRelativePath(storageRelativePath);
                attachment.setUrl( "/study-app/attachment/getAttachment/" + id);
                attachmentService.save(attachment);
                list.add(attachment);
            } catch (IOException e) {
                System.out.println("文件上传失败！");
                e.printStackTrace();
            }
        }
        return list;
    }

    private File doUpload(MultipartFile multipartFile, String storageRelativePath) throws IOException {
        ErrorUtil.notBlank(storageRelativePath, "相对存储路径不能为空！");
        // 绝对路径
        String storePath = getAbsoluteFilePath(storageRelativePath);
        FileUtil.mkParentDirs(storePath);
        // 上传
        File dest = new File(storePath);
        multipartFile.transferTo(dest);
        return dest;
    }

    private String getAbsoluteFilePath(String relativePath) {
        Assert.notBlank(relativePath);
        String basePath = this.baseDir;
        if (!basePath.endsWith("/")) {
            basePath = basePath + "/";
        }
        if (relativePath.startsWith("/")) {
            relativePath = relativePath.replaceFirst("/", "");
        }
        return basePath + relativePath;
    }


    private String getContentType(File file) {
        Assert.notNull(file);
        // 利用nio提供的类判断文件ContentType
        String contentType = null;
        try {
            contentType = Files.probeContentType(file.toPath());
        } catch (IOException e) {
            // do nothing
        }
        // 若失败则调用另一个方法进行判断
        if (contentType == null) {
            contentType = new MimetypesFileTypeMap().getContentType(file);
        }
        log.debug("contentType：{}", contentType);
        return contentType;
    }

}
